(: Author: Anthony B. Coates, Miley Watts LLP. :)
(: http://www.mileywatts.com/ :)
(: Released under the terms of the Mozilla license version 1.1. :)
(: http://www.mozilla.org/MPL/MPL-1.1.html :)

(: Does not process Properties and Associations yet. :)

declare namespace fn = "http://xml.mileywatts.com/ns/2007/iso20022/transform/xquery";
declare namespace uml = "http://schema.omg.org/spec/UML/2.0";
declare namespace xmi = "http://schema.omg.org/spec/XMI/2.1";
declare namespace UnifiOntologyMetamodel = "http://www.magicdraw.com/schemas/UnifiOntologyMetamodel.xmi";
declare namespace msg = "http://xml.mileywatts.com/ns/2007/iso20022/ontology/intermediate/0.1";

(: Path (relative or absolute) to the W3C XML Schema from the intermediate XML file (output file). :)
declare variable $PathToSchema as xs:string external;

(: Whether to include XMI IDs in the output. :)
declare variable $ShowXmiIDs as xs:boolean := true();

(: Detects the type of each "ownedMember" and generates an appropriate intermediate XML entry. :)
(: The need to reject bogus anonymous components
   is because of an issue in the generated XMI that is being used for testing. :)
declare function fn:generate-entries($ownedMembers as node()*, $xmi as node()) as node()*
{
  let $messages :=
    for $message in $xmi/UnifiOntologyMetamodel:message
    for $class in $ownedMembers[@xmi:type = 'uml:Class'][@name != '']
    where $class/@xmi:id = $message/@base_Class
    return $class
  let $messageComponents :=
    for $class in $ownedMembers[@xmi:type = 'uml:Class'][@name != ''] except $messages
    return $class
  let $bogusAnonymousComponents :=
    for $class in $ownedMembers[@xmi:type = 'uml:Class'][@name = '']
    return $class
  let $dataTypes := $ownedMembers[@xmi:type = "uml:DataType"]
  let $primitiveTypes := $ownedMembers[@xmi:type = 'uml:PrimitiveType']
  (: The variables below here are just so we can detect unrecognised ownedMembers. :)
  let $associations := $ownedMembers[@xmi:type = 'uml:Association']
  let $comments := $ownedMembers[@xmi:type = 'uml:Comment']
  let $generalizations := $ownedMembers[@xmi:type = 'uml:Generalization']
  let $literalIntegers := $ownedMembers[@xmi:type = 'uml:LIteralInteger']
  let $models := $ownedMembers[@xmi:type = 'uml:Model']
  let $packages := $ownedMembers[@xmi:type = 'uml:Package']
  let $profiles := $ownedMembers[@xmi:type = 'uml:Profile']
  let $properties := $ownedMembers[@xmi:type = 'uml:Property']
  let $stereotypes := $ownedMembers[@xmi:type = 'uml:Stereotype']
  let $recognisedMembers :=
    (
      $messages,
      $messageComponents,
      $bogusAnonymousComponents,
      $dataTypes,
      $primitiveTypes,
      $associations,
      $comments,
      $generalizations,
      $literalIntegers,
      $models,
      $packages,
      $profiles,
      $properties,
      $stereotypes
    )
  let $unrecognisedMembers := $ownedMembers except $recognisedMembers
  return
    (
      fn:generate-message-entries($messages, $ownedMembers),
      fn:generate-message-component-entries($messageComponents, $ownedMembers),
      fn:generate-data-type-entries($dataTypes, $ownedMembers),
      fn:generate-primitive-type-entries($primitiveTypes, $ownedMembers),
      fn:generate-unrecognised-member-entries($unrecognisedMembers, $ownedMembers)
    )
  };

(: Generates the intermediate XML for a message. :)
declare function fn:generate-message-entries($messages as node()*, $ownedMembers as node()*) as node()*
{
  for $class in $messages
  order by $class/@name
  return
    <Message>
      {
        fn:insert-class-entry-common($class, $ownedMembers),
        fn:insert-elements($class, $ownedMembers)
      }
    </Message>
};

(: Generates the intermediate XML for a message component. :)
declare function fn:generate-message-component-entries($messageComponents as node()*, $ownedMembers as node()*) as node()*
{
  for $class in $messageComponents
  order by $class/@name
  return
  <MessageComponent>
      {
        fn:insert-class-entry-common($class, $ownedMembers),
        fn:insert-elements($class, $ownedMembers)
      }
    </MessageComponent>
};

(: Generates the intermediate XML for a data type. :)
declare function fn:generate-data-type-entries($dataTypes as node()*, $ownedMembers as node()*) as node()*
{
  for $type in $dataTypes
  order by $type/@name
  return
    <DataType>
      { fn:insert-class-entry-common($type, $ownedMembers) }
    </DataType>
};

(: Generates the intermediate XML for a primitive type. :)
declare function fn:generate-primitive-type-entries($primitiveTypes as node()*, $ownedMembers as node()*) as node()*
{
  for $type in $primitiveTypes
  order by $type/@name
  return
    <PrimitiveType>
      { fn:insert-class-entry-common($type, $ownedMembers) }
    </PrimitiveType>
};

(: Generates the intermediate XML for a primitive type. :)
declare function fn:generate-unrecognised-member-entries($unrecognisedMembers as node()*, $ownedMembers as node()*) as node()*
{
  for $member in $unrecognisedMembers
  order by $member/@name
  return
    <Unrecognised>
      {
        fn:insert-class-entry-common($member, $ownedMembers),
        <XmiType>
          {
            if (exists($member/@xmi:type))
            then string($member/@xmi:type)
            else
              if (exists($member/type))
              then string($member/type/@href)
              else ()
          }
        </XmiType>
      }
    </Unrecognised>
};

(: Generates the hierarchical package of an "ownedMember" class definition. :)
declare function fn:insert-package($ownedMember as node(), $isTruncate as xs:boolean) as xs:string?
{
  for $parent in $ownedMember/..[self::ownedMember[(@xmi:type = 'uml:Package') or (@xmi:type = 'uml:Model')]]
  let $xmiType := $parent/@xmi:type
  let $package :=
    concat(
      fn:insert-package($parent, false()),
      if (($xmiType = "uml:Package") or ($xmiType = "uml:Model")) then string($parent/@name) else "",
      "::"
    )
  return
    if ($isTruncate)
    then
      if (string-length($package) >= 3)
      then substring($package, 1, string-length($package)-2)
      else ()
    else $package
};

(: Generates intermediate XML for a class derivation hierarchy. :)
declare function fn:insert-generalizations($ownedMember as node(), $ownedMembers as node()*) as node()?
{
  let $baseClass :=
    if (exists($ownedMember/generalization))
    then $ownedMembers[@xmi:id = $ownedMember/generalization/@general]
    else ()
  return
    if (exists($baseClass))
    then
      <BasedOn>
        { fn:insert-class-entry-common($baseClass, $ownedMembers) }
      </BasedOn>
    else ()
};

(: Generates common intermediate XML for different class, type, package, or model definitions. :)
declare function fn:insert-class-entry-common($ownedMember as node(), $ownedMembers as node()*) as node()*
{
  let $comment := $ownedMember/ownedComment[@xmi:type='uml:Comment']
  let $package := fn:insert-package($ownedMember, true())
  let $isAbstract := exists($ownedMember/@isAbstract) and (string($ownedMember/@isAbstract) = 'true')
  return
    (
      attribute Name {$ownedMember/@name},
      if (exists($package)) then attribute Package { $package } else (),
      if ($isAbstract) then attribute Abstract { true() } else (),
      if ($ShowXmiIDs) then attribute xmi.id {$ownedMember/@xmi:id} else (),
      if (exists($comment))
      then element Documentation { string($comment/@body) }
      else (),
      fn:insert-generalizations($ownedMember, $ownedMembers)
    )
};

(: Generates element entries for the intermediate XML. :)
(: The need to reject properties with bogus anonymous types
   is because of an issue in the generated XMI that is being used for testing.
   This causes a noticeable performance hit. :)
declare function fn:insert-elements($class as node(), $ownedMembers as node()*) as node()*
{
  for $property in $class/ownedAttribute[@xmi:type = 'uml:Property']
  return
    if (exists($property/type) or exists($ownedMembers[(@xmi:id = $property/@type) and (@name != '')]))
    then
      <Element>
        { fn:insert-property-entry-common($property, $ownedMembers) }
      </Element>
    else ()
};

(: Generates common intermediate XML for property (element/attribute) definitions. :)
declare function fn:insert-property-entry-common($property as node(), $ownedMembers as node()*) as node()*
{
  let $propertyName := string($property/@name)
  let $typeClass :=
    if (exists($property/@type))
    then $ownedMembers[@xmi:id = $property/@type]
    else
      if (exists($property/type[(@xmi:type = 'uml:DataType') or (@xmi:type = 'uml:PrimitiveType')]))
      then $ownedMembers[@xmi:id = substring-after(string($property/type/@href), '#')]
      else ()
  let $typeName := string($typeClass/@name)
  let $typePackageName := if (exists($typeClass)) then fn:insert-package($typeClass, true()) else ()
  let $comment := $property/ownedComment[@xmi:type='uml:Comment']
  return
  (
    attribute Name
    {
      if ($propertyName != "")
      then $propertyName
      else
        concat(
          lower-case(substring($typeName,1,1)),
          substring($typeName, 2)
        )
    },
    attribute Type { $typeName },
    if (exists($typePackageName)) then attribute TypePackage { $typePackageName } else (),
    attribute MinOccurs
    { 
      if (exists($property/lowerValue))
      then string($property/lowerValue/@value)
      else "1"
    },
    attribute MaxOccurs
    {
      if (exists($property/upperValue))
      then
        if (($property/upperValue/@value = "*") or ($property/upperValue/@value = "n"))
        then "unbounded"
        else string($property/upperValue/@value)
      else "1"
    },
    if ($ShowXmiIDs) then attribute xmi.id {$property/@xmi:id} else (),
    if (exists($comment))
    then element Documentation { string($comment/@body) }
    else ()
  )
};

for $xmi in /xmi:XMI
let $ownedMembers :=
  for $member in $xmi//ownedMember
  return $member
return
  <msg:MessageSet
    xmlns:msg="http://xml.mileywatts.com/ns/2007/iso20022/ontology/intermediate/0.1"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xml.mileywatts.com/ns/2007/iso20022/ontology/intermediate/0.1 {$PathToSchema}">
    { fn:generate-entries($ownedMembers, $xmi) }
  </msg:MessageSet>
